package com.daji.concurrency.multithread_future_callback;

import java.util.concurrent.CompletableFuture;

/*
    CompletableFuture 这个知识点极其重要。重要到什么程度呢？
    这一个知识点可以串联起：
    回调机制、异步回调、同步回调、异步和同步的概念、Lambda表达式、Function函数式接口、
    线程池、双冒号语法、阻塞与非阻塞……


*/

/*
    这个例子极其重要。前置知识有回调机制
    回调机制，我已经在这个工程下的 callback_and_lambda 这个包下详解了。

    引入：
    E01_Future这个例子中，主线程想要获取子线程的返回结果，
    使用的是：future.get();这个方法，该方法是阻塞的： 阻塞主线程直到任务返回结果。
    也就是说虽然使用了线程（异步），但是如果想返回结果，仍然是阻塞的。

    如果我们引入回调机制会怎么样？异步+回调，进一步提高并发度。
    所以下面就介绍了一种异步回调的机制：CompletableFuture
*/
public class E02_CompletableFuture {
    public static void main(String[] args) throws Exception {
        // 创建异步执行任务:

        /*
            加餐：Java :: 双冒号表达式
            官方名字叫方法引用。
            该功能特性也是JDK8以后引入的，你可以把它看做lambda表达式的语法糖。
            list.forEach((t) -> System.out.println(t)); 这句话等同于：System.out::println
            这个双冒号只不过是个语法糖，不是重点。而后面的Function接口包，Lambda，回调机制等等才是重中之重
        */
        /*
            观察源代码，我们可以知道这个CompletableFuture.supplyAsync接收的是一个Supplier
            Supplier是Java8为我们提供的函数式接口包提供给我们的，该接口特点是无入参有返回值。所以最前面的那个（）是空的。
            也就是说我们可以将“一坨代码”传入 CompletableFuture.supplyAsync 中。所以说，这并不是一个新奇的概念。

            这并不是什么新奇的概念。
            还记得 public void run吗？最最原始的开线程的方法：
            我们可以继承Runnable接口。
            Thread t3 = new Thread(() -> func());
            之前在A01_HowToCreateThread中写过一个Demo，将一个函数传入lambda中。
            继承的Runnable接口我们来看看它的定义：

            @FunctionalInterface
            public interface Runnable {
                void run();
            }

            实际上你传入的是一个 void run() 注意这里的void

            还记得上一节（E01_Future）中说过的：
            开线程可以实现Runnable，也可以实现Callable
            Callable有个好处，就是这个线程执行完毕之后有返回值。我们现在看看Callable的定义：

            @FunctionalInterface
            public interface Callable<V> {
                V call() throws Exception;
            }

            它和Runnable长得简直是太像了！！！！！！只不过它不是void，而是有返回值。

            现在我们回到 CompletableFuture.supplyAsync 这一行代码。
            这一行代码，其实只是用朴素的方式，开了个线程罢了。。线程里要执行的那“一坨代码”，就是fetchPrice()这个函数

            就这么简单。（廖雪峰老师说CompletableFuture是提交给默认的线程池执行，也就是说是使用线程池方式执行的fetchPrice() ）

            那说到这里，CompletableFuture.supplyAsync只不过是开线程罢了，根本没有涉及到回调和返回值
            返回值呢？回调机制呢？异步非阻塞对返回值进行回调处理呢？

            别着急，马上就讲到了。
        */
        //下面的代码可以简写为双冒号： CompletableFuture<Double> cf = CompletableFuture.supplyAsync(E02_CompletableFuture::fetchPrice);
        CompletableFuture<Double> cf = CompletableFuture.supplyAsync(()->E02_CompletableFuture.fetchPrice());

        //下面这一行代码会报错。因为Supplier必须有返回值。而我sout(123)并不会有返回值
        //CompletableFuture.supplyAsync(()-> System.out.println(123));
        //但是我修改成下面这样就不报错了，因为Math.max(1,2)有返回值，符合Supplier的条件。
        //CompletableFuture<Integer> cb = CompletableFuture.supplyAsync(() -> Math.max(1, 2));


        /*
            终于到了喜闻乐见的 异步非阻塞对返回值进行回调处理 环节！！！
            如果看懂了前面的，这里简直不要太简单了。

            thenAccept方法接受一个Consumer参数。就是Function包下的那个Consumer，consumer的特点是有参无返回值

            如果执行成功了，子线程会回调下面的方法。将 fetchPrice() 的返回结果传入consumer，执行一次回调！
        */

        // 如果执行成功:  thenAccept 负责线程执行完后异步回调。
        cf.thenAccept((result) -> {
            System.out.println("回调函数已执行");
            System.out.println("price: " + result);
        });

        // 如果执行异常(也执行一次回调):
        cf.exceptionally((e) -> {
            e.printStackTrace();
            System.out.println("异常的回调函数已执行");
            return null;
        });
        System.out.println("主线程会先于回调函数执行。");
        // 主线程不要立刻结束，否则CompletableFuture默认使用的线程池会立刻关闭:
        Thread.sleep(200000);
    }

     static Double fetchPrice() {
        try {
            System.out.println("计算价格的时间比较长………………请稍后");
            Thread.sleep(10000);
        } catch (InterruptedException e) {
        }
        if (Math.random() < 0.3) {
            throw new RuntimeException("fetch price failed!");
        }
        return 5 + Math.random() * 20;
    }
}
/*
    更神的地方还在后面。
    CompletableFuture还可以串行化任务，并行化任务。
    接着看E03_CompletableFuture 和 E04_CompletableFuture的使用场景

    E04_CompletableFuture的使用场景，真可谓是大成中的大成，如果E04懂了，所有知识点都串起来了。
*/